﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Runtime.Serialization.Formatters.Binary;

namespace Skipette
{
    public class Server
    {
        static List<TcpClient> waitingConnection = new List<TcpClient>();
        public static TcpListener tcpListener = new TcpListener(IPAddress.Any, 3000);
        static Thread listenThread = new Thread(new ThreadStart(ListenForClients));
        static BinaryFormatter bf = new BinaryFormatter();
        public static String serverIp;

        public static void InitializeListener()
        {
            listenThread.Start();
        }

        private static void ListenForClients()
        {
            tcpListener.Start();

            while (!Program.exiting)
            {
                Console.WriteLine("Waiting for new connection...");
                //blocks until a client has connected to the server
                TcpClient client = null;
                try
                {
                    client = tcpListener.AcceptTcpClient();
                    //create a thread to handle communication 
                    //with connected client
                    Console.WriteLine("> Connection ESTABLISHED");
                    Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientCon));
                    clientThread.Start(client);
                }
                catch { }
            }
        }

        private static void HandleClientCon(object objClient)
        {

            TcpClient client = (TcpClient)objClient;
            NetworkStream clientStream = client.GetStream();

            byte[] message = new byte[4096];
            int bytesRead;
            ASCIIEncoding encoder = new ASCIIEncoding();
            bool keepOpen = false;
            while (true)
            {
                bytesRead = 0;

                try
                {
                    Console.WriteLine("> Waiting for response...");
                    //blocks until a client sends a message
                    bytesRead = clientStream.Read(message, 0, 4096);
                }
                catch
                {
                    //a socket error has occured
                    break;
                }

                if (bytesRead == 0)
                {
                    //the client has disconnected from the server
                    break;
                }

                //message has successfully been received
                String received = (encoder.GetString(message, 0, bytesRead)).Split(new String[] { ":END" }, StringSplitOptions.None)[0];
                int convId = Int32.Parse(received.Split(new String[] { "*SKBREAKER*" }, StringSplitOptions.None)[0]);
                bool found = false;
                foreach (Conversation c in Program.conversations)
                {
                    if (c.convId == convId)
                    {
                        c.ReceiveMessage(received);
                        found = true;
                        break;
                    }
                }
                if (!found)
                {
                    Conversation c = new Conversation(convId);
                    Program.conversations.Add(c);
                    c.ReceiveMessage(received);
                }
            }

            if (!keepOpen)
            {
                client.Close();
                Console.WriteLine("> Client DISCONNECTED");
            }
        }

        public static void UpdateStatus(Account acc, Skipette.Program.STATUS newStat)
        {

        }

        public static void UpdateStatusMessage(Account acc, String newMessage)
        {

        }

        public static bool Authenticate(String userName, String password)
        {
            return true; // DUMMY
        }

        public static Account GetAccount(String userName)
        {
            Account result = null;
            try
            {
                //TRY TO GET ACC FROM SERVER
            }
            catch
            {
                foreach (CashedInfo c in Program.cache)
                {
                    if (c.username.Equals(userName))
                    {
                        result = c.LoadToAcc();
                        break;
                    }
                }
            }
            if (result == null)
            {
                //DUMMY
                result = new Account(true);
                result.username = "ronnin426850";
                result.firstName = "Daniel";
                result.surname = "Stefanov";
                Account f1 = new Account(true);
                f1.username = "friend1";
                f1.firstName = "Friend 1";
                f1.friends.Add(result);
                Account f2 = new Account(true);
                f2.username = "friend2";
                f2.firstName = "Friend 2";
                f2.friends.Add(result);
                Account f3 = new Account(true);
                f3.username = "friend3";
                f3.firstName = "Friend 3";
                f3.friends.Add(result);
                result.friends.AddRange(new Account[] { f1, f2, f3 });
            }
            return result;
        }

        public static String GetLastIP(Account acc)
        {
            //GET IP

            //CONNECT TO SERVER TO GET IP

            String result = null;
            bool alreadyCached = false;
            foreach (CashedInfo ci in Program.cache)
            {
                if (ci.username!=null && ci.username.Equals(acc.username))
                {
                    result = ci.lastIp;
                    alreadyCached = true;
                    break;
                }
            }
            if (alreadyCached)
                return result;
            else
                result = "127.0.0.1"; //DUMMY - to say "error"

            //CACHE IP
            alreadyCached = false;
            foreach(CashedInfo ci in Program.cache)
            {
                if (ci.username != null && ci.username.Equals(acc.username))
                {
                    ci.lastIp = result;
                    alreadyCached = true;
                    break;
                }
            }
            if(!alreadyCached)
            {
                CashedInfo ci = new CashedInfo();
                ci.LoadFromAcc(acc);
                ci.lastIp = result;
                Program.cache.Add(ci);
            }

            //RETURN
            return result;
        }

        public static void SendMessageTo(Account acc, String message, int convId)
        {
            message = acc.username + "*SKBREAKER*" + DateTime.Now.ToString() + "*SKBREAKER*" + message + ":END";
            message = convId.ToString() + "*SKBREAKER*" + message;
            //POSSIBLY ENCRIPT
            try
            {
                String ip = GetLastIP(acc);
                if (ip.Equals("error"))
                    throw new InvalidOperationException();
                TcpClient remote = new TcpClient(ip, 3000);
                ASCIIEncoding encoder = new ASCIIEncoding();
                byte[] buffer;
                buffer = encoder.GetBytes(message);
                remote.GetStream().Write(buffer, 0, buffer.Length);
                remote.GetStream().Close();
                remote.Close();
            }
            catch
            {
                //CONNECT TO SERVER TO LEAVE MESSAGE FOR LATER DELIVERY
            }
        }

        public static Image GetProfilePic(String userName)
        {
            Image result = null;
            try
            {
                //TRY TO GET FROM SERVER
            }
            catch 
            {
                foreach (CashedInfo c in Program.cache)
                {
                    if (c.username.Equals(userName) && c.profilePic != null)
                    {
                        result = c.profilePic;
                        break;
                    }
                }
            }
            if (result == null)
                result = Properties.Resources.not_available;
            return result;
        }
    }
}
